﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using KSS.Generator.Configuration;
using KSS.Generator.Schema;

namespace KSS.Generator
{
    public class DataProjectGenerator : ProjectGenerator
    {
        public override string ProjectName
        {
            get { return Template.Project.DataProjectName; }
        }

        protected override void GenerateFile(ProjectSetting projectConfig, FileInfo templateFile, DirectoryInfo generatedDirectory)
        {
            if (Template.DataProject.ProviderFileName.Equals(templateFile.Name, StringComparison.OrdinalIgnoreCase))
            {
                GenerateProviderFile(projectConfig, templateFile, generatedDirectory);
            }
        }

        private void GenerateProviderFile(ProjectSetting projectConfig, FileInfo templateFile, DirectoryInfo generatedDirectory)
        {
            // Provider file name
            string providerFileName = GeneratorHelper.GetFileNameFromat(templateFile.Name);

            // Get entity mapper template file content
            string templateContent = GeneratorHelper.ReadFileContent(generatedDirectory, providerFileName);

            StringBuilder sbGeneratedMethods = new StringBuilder();
            Column associationColumn = null;

            foreach (Table tableSchema in Context.DatabaseSchema.Tables)
            {
                #region { Provider methods }

                // entity name, also entity type
                string entityName = tableSchema.DataType.Name;

                List<Column> primaryColums = tableSchema.DataType.Columns.FindAll(
                  delegate(Column c)
                  {
                      return (c.IsPrimaryKey);
                  });

                List<Association> foreignAssociations = tableSchema.DataType.Associations.FindAll(
                       delegate(Association a)
                       {
                           return (a.IsForeignKey);
                       });

                #region { Region start }
                // Region start
                sbGeneratedMethods.AppendLine(string.Format(Template.Tab2 + Template.DataProject.EntityRegionStart, entityName));
                #endregion

                #region { GetCount }
                // GetCount
                sbGeneratedMethods.AppendLine(string.Format(Template.Tab2 + Template.DataProject.GetCount, entityName));
                #endregion

                #region { GetByPrimaryKey }
                // GetByPrimaryKey
                if (primaryColums.Count == 1)
                {
                    foreach (Column c in primaryColums)
                    {
                        sbGeneratedMethods.AppendLine(string.Format(
                                    Template.Tab2 + Template.DataProject.GetByPrimaryKey,
                                    entityName,
                                    c.Name,
                                    c.Type,
                                    GeneratorHelper.GenerateLocalVariableName(c.Name)));
                    }
                }
                #endregion

                #region { GetListByPrimaryKey }
                // GetListByPrimaryKey
                if (primaryColums.Count > 1)
                {
                    foreach (Column c in primaryColums)
                    {
                        sbGeneratedMethods.AppendLine(string.Format(
                                        Template.Tab2 + Template.DataProject.GetListByPrimaryKey,
                                        entityName,
                                        c.Name,
                                        c.Type,
                                        GeneratorHelper.GenerateLocalVariableName(c.Name)));
                    }
                }
                #endregion

                #region { GetListByPrimaryKeyWithPaging }
                // GetListByPrimaryKeyWithPaging
                if (primaryColums.Count > 1)
                {
                    foreach (Column c in primaryColums)
                    {
                        sbGeneratedMethods.AppendLine(string.Format(
                                    Template.Tab2 + Template.DataProject.GetListByPrimaryKeyWithPaging,
                                    entityName,
                                    c.Name,
                                    c.Type,
                                    GeneratorHelper.GenerateLocalVariableName(c.Name)));
                    }
                }
                #endregion

                #region { GetByForeignKeyId }

                // GetByForeignKeyId                
                foreach (Association a in foreignAssociations)
                {
                    associationColumn = tableSchema.DataType.Columns.Find(
                            delegate(Column c)
                            {
                                return (c.Name == a.ThisKey);
                            });

                    if (associationColumn == null)
                    {
                        continue;
                    }

                    sbGeneratedMethods.AppendLine(string.Format(
                        Template.Tab2 + Template.DataProject.GetByForeignKey,
                        entityName,
                        a.ThisKey,
                        GeneratorHelper.GetTypeFromColumn(associationColumn),
                        GeneratorHelper.GenerateLocalVariableName(a.ThisKey)));
                }
                #endregion

                #region { GetByForeignKeyEntity }
                // GetByForeignKeyEntity
                foreach (Association a in foreignAssociations)
                {
                    sbGeneratedMethods.AppendLine(string.Format(
                        Template.Tab2 + Template.DataProject.GetByForeignKeyEntity,
                        entityName,
                        a.Type,
                        GeneratorHelper.GenerateLocalVariableName(a.Type)));
                }
                #endregion

                #region { GetByForeignKeyIdWithPaging }
                // GetByForeignKeyWithPaging
                foreach (Association a in foreignAssociations)
                {
                    associationColumn = tableSchema.DataType.Columns.Find(
                                               delegate(Column c)
                                               {
                                                   return (c.Name == a.ThisKey);
                                               });

                    if (associationColumn == null)
                    {
                        continue;
                    }

                    sbGeneratedMethods.AppendLine(string.Format(
                        Template.Tab2 + Template.DataProject.GetByForeignKeyWithPaging,
                        entityName,
                        a.ThisKey,
                        GeneratorHelper.GetTypeFromColumn(associationColumn),
                        GeneratorHelper.GenerateLocalVariableName(a.ThisKey)));
                }
                #endregion

                #region { GetByForeignKeyEntityWithPaging }
                // GetByForeignKeyEntityWithPaging
                foreach (Association a in foreignAssociations)
                {
                    sbGeneratedMethods.AppendLine(string.Format(
                        Template.Tab2 + Template.DataProject.GetByForeignKeyEntityWithPaging,
                        entityName,
                        a.Type,
                        GeneratorHelper.GenerateLocalVariableName(a.Type)));
                }
                #endregion

                #region { GetAll }
                // GetAll
                sbGeneratedMethods.AppendLine(string.Format(Template.Tab2 + Template.DataProject.GetAll, entityName));

                #endregion

                #region { GetAllWithPaging }
                // GetAllWithPaging
                sbGeneratedMethods.AppendLine(string.Format(Template.Tab2 + Template.DataProject.GetAllWithPaging, entityName));

                #endregion

                #region { Add }
                // Add
                sbGeneratedMethods.AppendLine(string.Format(Template.Tab2 + Template.DataProject.Add,
                                                    entityName,
                                                    GeneratorHelper.GenerateLocalVariableName(entityName)));
                #endregion

                #region { Update }
                // Update
                sbGeneratedMethods.AppendLine(string.Format(Template.Tab2 + Template.DataProject.Update,
                                                    entityName,
                                                    GeneratorHelper.GenerateLocalVariableName(entityName)));
                #endregion

                #region { DeleteByPrimaryKey }
                // DeleteByPrimaryKey
                foreach (Column c in primaryColums)
                {
                    sbGeneratedMethods.AppendLine(string.Format(
                            Template.Tab2 + Template.DataProject.DeleteByPrimaryKey,
                            entityName,
                            c.Name,
                            GeneratorHelper.GetTypeFromColumn(c),
                            GeneratorHelper.GenerateLocalVariableName(c.Name)));
                }
                #endregion

                #region { DeleteByPrimaryEntity }
                // DeleteByPrimaryEntity
                sbGeneratedMethods.AppendLine(string.Format(
                            Template.Tab2 + Template.DataProject.DeleteByPrimaryEntity,
                            entityName,
                            GeneratorHelper.GenerateLocalVariableName(entityName)));
                #endregion

                #region { DeleteByForeignKey }
                // DeleteByForeignKey
                foreach (Association a in foreignAssociations)
                {
                    associationColumn = tableSchema.DataType.Columns.Find(
                                              delegate(Column c)
                                              {
                                                  return (c.Name == a.ThisKey);
                                              });

                    if (associationColumn == null)
                    {
                        continue;
                    }

                    sbGeneratedMethods.AppendLine(string.Format(
                            Template.Tab2 + Template.DataProject.DeleteByForeignKey,
                            entityName,
                            a.ThisKey,
                            GeneratorHelper.GetTypeFromColumn(associationColumn),
                            GeneratorHelper.GenerateLocalVariableName(a.ThisKey)));
                }
                #endregion

                #region { DeleteByForeignEntity }
                // DeleteByForeignEntity
                foreach (Association a in foreignAssociations)
                {
                    sbGeneratedMethods.AppendLine(string.Format(
                            Template.Tab2 + Template.DataProject.DeleteByForeignEntity,
                            entityName,
                            a.Type,
                            GeneratorHelper.GenerateLocalVariableName(a.Type)));
                }
                #endregion

                #region { Region end }
                // Region end
                sbGeneratedMethods.AppendLine(Template.Tab2 + Template.DataProject.EndRegion);
                #endregion
                #endregion
            }

            #region { Store procedure methods }

            #region { Region start }
            // Region start
            sbGeneratedMethods.AppendLine(string.Format(Template.Tab2 + Template.DataProject.EntityRegionStart, "Store procedure"));
            #endregion

            foreach (Function f in Context.DatabaseSchema.Functions)
            {
                StringBuilder sbParameters = new StringBuilder();

                foreach (Parameter p in f.Parameters)
                {
                    if (sbParameters.Length > 0)
                    {
                        sbParameters.Append(", ");
                    }

                    sbParameters.AppendFormat(
                        Template.Global.Declaration,
                        (p.Direction == Template.Global.ParameterDirectionInOut) ? "ref" : string.Empty,
                        GeneratorHelper.GetNullableType(p.Type),
                        (string.IsNullOrEmpty(p.Param)) ? p.Name : p.Param);
                }

                sbGeneratedMethods.AppendLine(string.Format(
                                Template.Global.Tab2 + Template.DataProject.StoredProcedure,
                                f.Method,
                                GeneratorHelper.GetReturnType(Context, f),
                                sbParameters.ToString()));
            }

            #region { Region end }
            // Region end
            sbGeneratedMethods.AppendLine(Template.Tab2 + Template.DataProject.EndRegion);
            #endregion
            #endregion

            // Get generated code place holder
            string placeHolder = GeneratorHelper.GetPropertyPlaceHolder(Template.DataProject.GeneratedCode, PropertyType.Custom);

            if (templateContent.Contains(placeHolder) == false)
            {
                // Provider code already exist
                int start = templateContent.IndexOf(Template.DataProject.GeneratedCodeRegion);
                int end = templateContent.IndexOf(Template.DataProject.CustomCodeRegion);

                if (start >= 0 && end > start)
                {
                    StringBuilder sbPlaceHolder = new StringBuilder();
                    sbPlaceHolder.AppendLine(Template.DataProject.GeneratedCodeRegion);
                    sbPlaceHolder.AppendLine();
                    sbPlaceHolder.AppendLine(placeHolder);
                    sbPlaceHolder.Append(Template.Tab2 + Template.DataProject.EndRegion);
                    sbPlaceHolder.AppendLine();
                    sbPlaceHolder.AppendLine();
                    sbPlaceHolder.Append(Template.Tab2);

                    templateContent = templateContent.Remove(start, end - start);
                    templateContent = templateContent.Insert(start, sbPlaceHolder.ToString());
                }
            }

            // If provider file be generated at first time
            projectConfig.SetPropertyValue(Template.DataProject.GeneratedCode, sbGeneratedMethods.ToString());

            // Fill property in template
            string sourceCode = GeneratorHelper.FillProperties(projectConfig, templateContent);

            // Write source code to file
            string generatedFilePath = string.Format("{0}\\{1}",
                        generatedDirectory.FullName,
                        providerFileName);

            GeneratorHelper.WriteToFile(generatedFilePath, sourceCode);
        }
    }
}